package com.bestcem.xm.common.core.sign.grpc.core;

import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.exception.BusinessException;
import com.bestcem.xm.common.core.grpcplatform.dao.GrpcPlatformDao;
import com.bestcem.xm.common.core.grpcplatform.entity.GrpcPlatformDO;
import com.bestcem.xm.common.core.uitls.StringUtil;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.util.JsonFormat;
import io.grpc.Context;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;

@Slf4j
@Component
public class GrpcTemplate {
    private static final String TEMPLATE = "%s_platform_code_%s";
    public static final Context.Key<String> CONTEXT_KEY_IP = Context.key("ip");
    public static final Context.Key<String> CONTEXT_KEY_METHOD = Context.key("method");
    public static final Context.Key<String> CONTEXT_KEY_AUTHORIZATION = Context.key("Authorization");
    public static final ThreadLocal<Authorization> clientAuth = new ThreadLocal<>();

    /**
     * 钱包服务端的serverCode.
     */
    public static final String SEVER_CODE_PAY = "PAY";

    @Resource
    private GrpcPlatformDao grpcPlatformDao;

    /**
     * 统一配置是否验签
     */
    @Value("${app.sign.grpcSignEnabled:false}")
    private boolean signEnabled;

    private static final GrpcPlatformDO NULL_OBJ = new GrpcPlatformDO();

    /**
     * 本地缓存，1分钟过期
     */
    private final TimedCache<String, GrpcPlatformDO> platformCache = new TimedCache<>(DateUnit.MINUTE.getMillis());

    public String getServerCode(String code) {
        return String.format(TEMPLATE, "server", code);
    }

    public String getClientCode(String code) {
        return String.format(TEMPLATE, "client", code);
    }

    public GrpcPlatformDO getClientTemplate(String code) {
        String clientCode = getClientCode(code);
        return getTemplate(clientCode);
    }

    public GrpcPlatformDO getServerTemplate(String code) {
        String clientCode = getServerCode(code);
        return getTemplate(clientCode);
    }

    private GrpcPlatformDO getTemplate(String code) {
        GrpcPlatformDO grpcPlatformDO = platformCache.get(code, false);
        if (grpcPlatformDO == null) {
            synchronized (platformCache) {
                grpcPlatformDO = platformCache.get(code, false);
                if (grpcPlatformDO == null) {
                    grpcPlatformDO = grpcPlatformDao.findByCode(code);
                    if (grpcPlatformDO == null) {
                        platformCache.put(code, NULL_OBJ);
                    } else {
                        platformCache.put(code, grpcPlatformDO);
                    }
                }


            }
        }
        if (grpcPlatformDO == NULL_OBJ) {
            return null;
        }
        return grpcPlatformDO;

    }

    /**
     * 验签
     *
     * @param message grpc对象
     */
    public ServiceResult<?> checkSign(Message message) {
        // 统一配置是否验签
        if (!signEnabled) {
            return ServiceResult.success();
        }
        // 从context取出数据
        Context context = Context.current();
        String ip = GrpcTemplate.CONTEXT_KEY_IP.get(context);
        String method = GrpcTemplate.CONTEXT_KEY_METHOD.get(context);
        String authorization = GrpcTemplate.CONTEXT_KEY_AUTHORIZATION.get(context);
        String content = this.serialize(message);
        if (log.isInfoEnabled()) {
            log.info("check sign {} {} {} {}", ip, method, authorization, content);
        }
        Map<String, String> authorizationMap = this.decodeAuthorization(authorization);
        // 获取yaml里的配置信息
        String platformCode = authorizationMap.get("pcode");
        if (StrUtil.isEmpty(platformCode)) {
            return ServiceResult.fail("clientCode code required");
        }
        GrpcPlatformDO clientTemplate = getClientTemplate(platformCode);
        if (clientTemplate == null) {
            return ServiceResult.fail("platform code invalid");
        }
        if (!clientTemplate.isEnabled()) {
            return ServiceResult.success();
        }
        String[] allowIps = clientTemplate.getAllowIps();
        String[] secretKeys = clientTemplate.getSecretKeys();

        // 校验ip
        ServiceResult<?> checkIpResult = checkIp(ip, allowIps);
        if (!checkIpResult.isSuccess()) {
            return checkIpResult;
        }
        //校验sign
        ServiceResult<?> signResult = checkSign(secretKeys, method, content, authorizationMap);
        if (!signResult.isSuccess()) {
            return signResult;
        }

        return ServiceResult.success();
    }

    /**
     * 检查ip,添加了网段支持。
     */
    private ServiceResult<?> checkIp(String checkIp, String[] allowIps) {
        final String ip = StringUtil.trimToNull(checkIp);
        if (ip == null) {
            return ServiceResult.fail("remote ip required");
        }
        if (allowIps == null) {
            return ServiceResult.fail(StrUtil.format("ip[{}] not in whitelist", ip));
        }

        boolean allow = Arrays.stream(allowIps).anyMatch(allowIp -> allowIp.contains("/") ? isInRange(ip, allowIp) : allowIp.equals(ip));
        if (!allow) {
            return ServiceResult.fail(StrUtil.format("ip[{}] not in whitelist", ip));
        }
        return ServiceResult.success();
    }

    /**
     * 判断ip是否在指定网段
     *
     * @param ip   ip
     * @param cidr 网段
     * @return 在则是，不在则否
     */
    public static boolean isInRange(String ip, String cidr) {
        try {
            String[] ips = ip.split("\\.");
            int ipAddr = (Integer.parseInt(ips[0]) << 24)
                    | (Integer.parseInt(ips[1]) << 16)
                    | (Integer.parseInt(ips[2]) << 8) | Integer.parseInt(ips[3]);
            int type = Integer.parseInt(cidr.replaceAll(".*/", ""));
            int mask = 0xFFFFFFFF << (32 - type);
            String cidrIp = cidr.replaceAll("/.*", "");
            String[] cidrIps = cidrIp.split("\\.");
            int cidrIpAddr = (Integer.parseInt(cidrIps[0]) << 24)
                    | (Integer.parseInt(cidrIps[1]) << 16)
                    | (Integer.parseInt(cidrIps[2]) << 8)
                    | Integer.parseInt(cidrIps[3]);

            return (ipAddr & mask) == (cidrIpAddr & mask);
        } catch (Exception e) {
            log.error("[award] 判断白名单异常", e);
            return false;
        }

    }

    /**
     * 检查签名
     */
    private ServiceResult<?> checkSign(String[] secretKeys, String method, String content,
                                       Map<String, String> authorizationMap) {
        String ts = authorizationMap.get("ts");
        String nonce = authorizationMap.get("nonce");
        String sign = authorizationMap.get("sign");
        String pcode = authorizationMap.get("pcode");


        boolean authorized = Arrays.stream(secretKeys).anyMatch(secretKey -> {
            String data = String.join("\n", Arrays.asList(method, ts, nonce, content, secretKey, pcode));
            String checkedSign = DigestUtil.md5Hex(data);
            return Objects.equals(sign, checkedSign);
        });
        if (!authorized) {
            return ServiceResult.fail(StrUtil.format("Platform[code={}] sign error", pcode));
        }
        return ServiceResult.success();
    }

    /**
     * 作为客户端添加sign
     *
     * @param message    grpc对象
     * @param serverCode server分配的的platformCode
     */
    public void addSign(Message message, String serverCode) {
        // 如果没有服务的code默认不用加密
        if (StringUtil.isNullOrEmpty(serverCode)) {
            return;
        }
        GrpcPlatformDO serverTemplate = getServerTemplate(serverCode);
        if (serverTemplate == null || !serverTemplate.isEnabled() || serverTemplate.getSecretKeys() == null || serverTemplate.getSecretKeys().length == 0) {
            return;
        }
        // 准备加签要用到的数据
        Authorization authorization = new Authorization();
        authorization.setContent(serialize(message));
        authorization.setPlatformCode(serverTemplate.getPCode());
        authorization.setSecretKey(serverTemplate.getSecretKeys()[0]);
        // 添加至threadLocal
        clientAuth.set(authorization);
    }


    /**
     * Decode Authorization
     */
    public Map<String, String> decodeAuthorization(String authorization) {
        Map<String, String> result = new HashMap<>();

        authorization = StringUtil.trimToEmpty(authorization);
        String[] compoundParts = authorization.split(",");

        for (String compoundPart : compoundParts) {
            compoundPart = StringUtil.trimToEmpty(compoundPart);
            String[] parts = compoundPart.split("=");
            if (parts.length != 2) {
                continue;
            }
            result.put(parts[0], parts[1]);
        }

        return result;
    }

    private String serialize(MessageOrBuilder message) {
        try {
            String content = JsonFormat.printer().preservingProtoFieldNames().print(message);
            JSONObject json = JSONObject.parseObject(content);
            content = JSONObject.toJSONString(json, SerializerFeature.SortField, SerializerFeature.MapSortField);
            return content;
        } catch (InvalidProtocolBufferException e) {
            throw new BusinessException("serializable MessageOrBuilder failed", e);
        }
    }

    /**
     * 客户端加密数据存放类
     */
    @Getter
    @Setter
    public static class Authorization {
        /**
         * 要加密的数据
         */
        private String content;
        /**
         * 加密的key
         */
        private String secretKey;

        /**
         * 作为客户端的code
         */
        private String platformCode;
    }

    /**
     * 加密生成auth字符串
     */
    public static String generateAuth(String method) {
        //查询threadLocal里是否保存了数据。没有则不用生成签名
        Optional<Authorization> optional = Optional.ofNullable(clientAuth.get());
        if (!optional.isPresent() || !optional.map(Authorization::getContent).isPresent()
                || !optional.map(Authorization::getSecretKey).isPresent()
                || !optional.map(Authorization::getPlatformCode).isPresent()) {
            return null;
        }
        String content = optional.map(Authorization::getContent).get();
        String secretKey = optional.map(Authorization::getSecretKey).get();
        String platformCode = optional.map(Authorization::getPlatformCode).get();
        String ts = String.valueOf(System.currentTimeMillis());
        String nonce = RandomStringUtils.random(24, true, true);
        String data = String.join("\n", Arrays.asList(method, ts, nonce, content, secretKey, platformCode));
        String sign = DigestUtil.md5Hex(data);
        // 用完从threadLocal删除
        clientAuth.remove();
        return String.format("ts=%s,nonce=%s,sign=%s,pcode=%s", ts, nonce, sign, platformCode);
    }
}